Skip to content

Make cyclic values safe to format and clone - #623

Closed
logbie wants to merge 3 commits into
mainfrom
agent/handle-cyclic-values
Closed

Make cyclic values safe to format and clone#623
logbie wants to merge 3 commits into
mainfrom
agent/handle-cyclic-values

Conversation

@logbie

@logbie logbie commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • make Value::Display and Debug detect active-path list/object cycles
  • cap container formatting depth and avoid RefCell borrow panics during diagnostics
  • make deep_clone memoize mutable containers before descending
  • preserve cycles and shared identity inside the cloned graph while isolating it from the source
  • add Rust-level graph regressions plus an interpreter-level self-referential-list display test
  • document the behavior in the changelog and Dev Diary

Security impact

Valid WFL can insert a list into itself. Formatting or deeply cloning such a graph previously recursed until native stack exhaustion, which aborts the process rather than producing a catchable WFL error. Cycles now terminate deterministically as <cycle>, deep acyclic formatting stops at a bounded depth, and graph cloning reuses memoized placeholders.

Validation

  • git diff --check
  • regressions cover self-cycles, mutual list/object cycles, shared clone identity, source isolation, depth limiting, and WFL-level display
  • Rust tests were not run locally because this workspace has no Rust toolchain; repository CI passed on the final head
  • all GitHub CI, config lint, and review checks passed on the final head

Production readiness

  • area: Security / Reliability / Testing
  • advances the crash-resistance gate tracked in WFL Production Readiness: 8/10 by January 1, 2027 #610
  • compatibility impact: only cyclic, excessively deep, or concurrently borrowed values gain marker output; normal acyclic formatting remains unchanged

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@logbie, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 30 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c1c9ef27-898d-47dd-90d6-061f6888b36e

📥 Commits

Reviewing files that changed from the base of the PR and between 017e698 and 4776753.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • Dev diary/2026-07-16-cycle-safe-values.md
  • src/interpreter/tests.rs
  • src/interpreter/value.rs
  • tests/value_cycle_safety.rs
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent/handle-cyclic-values

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@logbie
logbie marked this pull request as ready for review July 16, 2026 18:13
Copilot AI review requested due to automatic review settings July 16, 2026 18:13

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens WFL’s Value formatting (Display/Debug) and deep_clone against cyclic container graphs, preventing stack overflows and making cloning preserve cycles/shared identity while isolating clones from sources—supporting reliability/security work tracked in #610.

Changes:

  • Add active-path cycle detection and a bounded depth limit to Value Display/Debug, plus try_borrow()-based formatting to avoid RefCell borrow panics for lists/objects.
  • Rework Value::deep_clone to memoize mutable container placeholders before descending, preserving cycles and aliasing within the cloned graph.
  • Add Rust-level regression tests for cyclic formatting/cloning and an interpreter-level test for displaying a self-referential list; document behavior in changelog and Dev Diary.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/interpreter/value.rs Implements cycle-safe + depth-bounded formatting state and memoized deep-clone of container graphs.
tests/value_cycle_safety.rs Adds Rust regression tests for self-cycles, mutual list/object cycles, shared identity preservation, and max-depth formatting.
src/interpreter/tests.rs Adds interpreter-level regression ensuring display of a self-referential list terminates safely.
Dev diary/2026-07-16-cycle-safe-values.md Documents the motivation, behavior changes, and test coverage for cycle-safe values.
CHANGELOG.md Notes the security/reliability behavior change for cyclic values in formatting and cloning.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/interpreter/value.rs
Comment on lines +285 to +324
fn deep_clone_container_instance(
instance: &Rc<RefCell<ContainerInstanceValue>>,
memo: &mut DeepCloneMemo,
) -> Rc<RefCell<ContainerInstanceValue>> {
let source_id = Rc::as_ptr(instance);
if let Some(cloned) = memo.container_instances.get(&source_id) {
return Rc::clone(cloned);
}

let cloned = Rc::new(RefCell::new(ContainerInstanceValue {
container_type: String::new(),
properties: HashMap::new(),
parent: None,
line: 0,
column: 0,
}));
memo.container_instances
.insert(source_id, Rc::clone(&cloned));

let source = instance.borrow();
let cloned_properties = source
.properties
.iter()
.map(|(key, value)| (key.clone(), value.deep_clone_with_memo(memo)))
.collect();
let cloned_parent = source
.parent
.as_ref()
.map(|parent| Self::deep_clone_container_instance(parent, memo));

*cloned.borrow_mut() = ContainerInstanceValue {
container_type: source.container_type.clone(),
properties: cloned_properties,
parent: cloned_parent,
line: source.line,
column: source.column,
};

cloned
}
Comment thread src/interpreter/value.rs
Comment on lines 398 to 402
f,
"Function({})",
func.name.as_ref().unwrap_or(&"anonymous".to_string())
func.name.as_deref().unwrap_or("anonymous")
)
}
Comment thread src/interpreter/value.rs
Comment on lines +531 to +534
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.fmt_display_with_state(f, &mut ValueFormatState::default(), 0)
}
Copilot AI review requested due to automatic review settings July 16, 2026 18:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Comment thread src/interpreter/value.rs
Comment on lines 277 to 279
Value::ContainerInstance(instance) => {
let inst = instance.borrow();
let cloned_properties = inst
.properties
.iter()
.map(|(k, v)| (k.clone(), v.deep_clone()))
.collect::<HashMap<_, _>>();
let cloned_parent = inst.parent.as_ref().map(|p| {
// Clone the parent reference, not deep clone (to avoid infinite recursion)
Rc::clone(p)
});
Value::ContainerInstance(Rc::new(RefCell::new(ContainerInstanceValue {
container_type: inst.container_type.clone(),
properties: cloned_properties,
parent: cloned_parent,
line: inst.line,
column: inst.column,
})))
Value::ContainerInstance(Self::deep_clone_container_instance(instance, memo))
}

logbie commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator Author

Superseded by #632, which preserves this security fix in the consolidated Rust-source hardening PR. The combined head is mergeable and all required CI checks are green.

@logbie logbie closed this Jul 17, 2026
@logbie
logbie deleted the agent/handle-cyclic-values branch August 14, 2026 04:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants